package cron.core.scheduler.timer;

import cron.core.thread.ThreadPoolConfig;
import cron.core.scheduler.trigger.Trigger;

import java.util.*;
import java.util.concurrent.Executor;

/**
 * 时间轮
 *
 * @author : willian fu
 * @version : 1.0
 * @date : 2021/3/9 0009 10:35
 */
public class TimeWheel {

    //用于调度Bucket任务的线程池
    private final Executor executor;

    //任务队列，时间轮容器
    private final List<Bucket> buckets;

    //时间刻度 ms
    private long timescale;

    //轮盘指针，指向哪个Bucket刻度
    private int pointer;

    //计数轮，记录转到第几轮
    private long count;

    //主轮询任务线程
    private Timer timer;

    //默认的时间精度 1s
    private final static long DEFAULT_TIMESCALE = 1000;

    //默认时间轮大小 10
    private final static int DEFAULT_BUCKET_SIZE = 10;

    public TimeWheel(final Executor executor, final List<Bucket> buckets, long timescale) {
        this.executor = executor;
        this.buckets = buckets;
        this.timescale = timescale;
    }

    public TimeWheel(final Executor executor, int bucketSize, long timescale) {
        this(executor, new ArrayList<>(bucketSize), timescale);
        for (int i = 0; i < bucketSize; i++) {
            buckets.add(new Bucket());
        }

    }

    public TimeWheel(int bucketSize, long timescale) {
        this(ThreadPoolConfig.getDefault(), new ArrayList<>(bucketSize), timescale);
        for (int i = 0; i < bucketSize; i++) {
            buckets.add(new Bucket());
        }
    }

    public TimeWheel(int bucketSize) {
        this(bucketSize, DEFAULT_TIMESCALE);
    }

    public TimeWheel() {
        this(DEFAULT_BUCKET_SIZE);
    }

    /**
     * 执行定时轮询
     */
    public TimeWheel start() {
        timer = new Timer();
        timer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                //System.out.print("*");
                doNext();
            }
        }, 0, timescale);
        return this;
    }

    /**
     * 向时间轮添加一个触发器
     *
     * @param trigger 触发器
     */
    public synchronized TimeWheel addTrigger(Trigger trigger) {
        //第几个周期后触发
        long unit = (trigger.getNextTime().getTime()
                - System.currentTimeMillis())
                / timescale;
        long zone = pointer + unit;
        zone = zone < 0 ? buckets.size() - 1 : zone;
        //需要触发的次数
        long tgCount = count + (int) zone / buckets.size();
        //应该放到第几个bucket
        int index = zone < buckets.size() ? (int) zone : (int) Math.abs(zone % buckets.size());
        //System.out.println("unit="+unit + "  pointer=" + pointer + "  count="+count + "  tgCount=" + tgCount + "  index="+index);
        Bucket bucket = buckets.get(index);
        if (null == bucket) {
            bucket = new Bucket();
        }
        bucket.addTrigger(trigger);
        trigger.setCount(tgCount);
        trigger.setTimeWheel(this);
        buckets.set(index, bucket);
        return this;
    }

    /**
     * 移除触发器
     *
     * @param trigger 触发器实例
     */
    public void removeTrigger(Trigger trigger) {
        buckets.forEach(bucket -> bucket.removeTrigger(trigger));
    }

    /**
     * 轮询
     */
    private void doNext() {
        Bucket bucket = buckets.get(pointer);
        if (null != bucket) {
            bucket.doRun(executor, count);
        }
        pointer++;
        if (pointer >= buckets.size()) {
            pointer = 0;
            count++;
        }
    }

}
